# -*- coding: UTF-8 -*-
from Code.Utils.sc_func import Common, Singleton, JsonParser
from Code.Model.sc_global import Global
from Code.Model.Data.sc_ssh import SSHUtil
from Code.Model.Data.sc_data import model_gate, DataInterface
from Code.Model.Controller.sc_logger import logger

language_data = model_gate.query_language_data


@Singleton
class TaskHandler(object):
    """ 任务执行处理类 """
    def __init__(self):
        self.script = None
        self.root = None
        self.task = None
        self.caller = None
        self.lock_format = None
        self.upload_offset = 25
        self.exec_offset = 50
        self.down_offset = 25
        self.interrupt_ips = []

    def init(self, script, root):
        self.caller = "{}/{}".format(model_gate.settings_data.server_dir, Global.G_FUNC_CALLER)
        self.script = script
        self.root = root
        self.task = script.split('.')[0].upper()
        self.interrupt_ips = []
        self.lock_format = '{}\\{}-%s.lock'.format(Global.G_PID_DIR, self.task)

    def lock(self, types):
        """ 任务并发执行锁 """
        if Common.is_file(self.lock_format % types):
            return False
        Common.write_to_file(self.lock_format % types, '')
        return True

    def unlock(self, types):
        """ 界面切换时会判断任务锁, 因此解锁的必然是同一任务锁 """
        Common.remove(self.lock_format % types)
        return True

    def start_task(self, ssh, params):
        cmd = "{} start_task {} {} {}".format(self.caller, self.task, self.script, params)
        SSHUtil.exec_ret(ssh, cmd, self.root)

    def stop_task(self, ssh):
        cmd = "{} stop_task {} {} &".format(self.caller, self.task, self.script)
        SSHUtil.exec_ret(ssh, cmd, True)

    def check_result(self, ssh, ip):
        def parse_progress(progress_info):
            if not progress_info:
                raise Exception(language_data("GET", "PROGRESS", "FAILED"))
            data = JsonParser.loads(progress_info)
            _progress = int(int(data['progress']) / 100 * self.exec_offset + self.upload_offset)
            return _progress, data['status'], data['info'], data['download']

        def is_finish():
            return progress == self.upload_offset + self.exec_offset

        progress_cmd = "{} get_task_progress {}".format(self.caller, self.task)
        printout_cmd = "{} get_task_printout {}".format(self.caller, self.task)
        values_cmd = "{} get_task_values {}".format(self.caller, self.task)
        period, retry, last, state = 0.5, 0, 0, True
        progress, status, info, download, printout = self.upload_offset, '', '', '', ''

        while True:
            Common.sleep(period)
            try:
                progress, status, info, download = parse_progress(self.get_execute_out(ssh, progress_cmd))
                printout = self.get_execute_out(ssh, printout_cmd)
                if status == 'FAILED':
                    state = False
                    break
                if last == progress:
                    continue
                last = progress
                if is_finish():
                    break
                if ip in self.interrupt_ips:
                    return
                DataInterface.update_progress_data(ip, progress, True)
                DataInterface.update_infotext_data(ip, self.task, info, True)
                DataInterface.update_printout_data(ip, printout)
            except Exception as e:
                if (retry := retry + 1) < model_gate.settings_data.retry_times:
                    continue
                if not isinstance(progress, int):
                    progress = self.upload_offset
                if not printout:
                    printout = self.get_execute_out(ssh, printout_cmd)
                info, state = e, False
                break

        if ip in self.interrupt_ips:
            return
        DataInterface.update_progress_data(ip, progress, state)
        DataInterface.update_infotext_data(ip, self.task, info, state)
        DataInterface.update_printout_data(ip, printout)
        if download not in DataInterface.all_is_none:
            state = self.download_file(ssh, ip, download)
        DataInterface.update_widgets_data(ip, self.get_execute_out(ssh, values_cmd))
        DataInterface.update_progress_data(ip, 100, state)
        stat = 'SUCCESS' if state else 'FAILED'
        DataInterface.update_infotext_data(ip, self.task, language_data('TASK', stat), state)

    def download_file(self, ssh, ip, file):
        def download_back(current, total):
            size[0] = current
            size[1] = total

        def get_progress():
            return int(size[0] / size[1] * self.down_offset + self.upload_offset + self.exec_offset)

        def update_progress():
            while True:
                if isinstance(state, bool):
                    break
                DataInterface.update_progress_data(ip, get_progress(), True)
                Common.sleep(0.3)

        size, state = [0, 1], None
        local = "{}\\{}\\{}".format(Global.G_DOWNLOAD_DIR, ip, Common.basename(file))
        info = '{} {}'.format(language_data('DOWNLOAD'), local)

        DataInterface.update_infotext_data(ip, self.task, info, True)
        Common.create_thread(func=update_progress, args=())

        state, err = SSHUtil.download_file(ssh, file, local, download_back)
        if state:
            DataInterface.update_infotext_data(ip, self.task, language_data('DOWNLOAD', 'SUCCESS'), True)
            return True
        info = '{} {}'.format(language_data('DOWNLOAD', 'FAILED'), str(err))
        DataInterface.update_infotext_data(ip, self.task, info, False)
        logger.error("{} download {} failed: {}".format(ip, file, str(err)))
        return False

    def upload_file(self, ssh, ip, uploads):
        """ 不直接在上传的callback中调用更新状态的Caller,因为会影响上传的速度，
        callback中只更新内存，状态更新在一个线程中定时去调用
        """
        if not uploads:
            return True

        def upload_back(current, total):
            size[0] = current

        def get_progress():
            return int((size[0] + size[1]) / total_size * self.upload_offset)

        def update_progress():
            while True:
                if isinstance(state, bool):
                    break
                DataInterface.update_progress_data(ip, get_progress(), True)
                Common.sleep(0.3)
            DataInterface.update_progress_data(ip, get_progress(), state)

        size, total_size, state = [0, 0], 0, None
        server_upload = Global.G_SERVER_UPLOAD % model_gate.settings_data.server_dir
        for local in uploads:
            total_size += Common.file_size(local)
        Common.create_thread(func=update_progress, args=())

        for local in uploads:
            remote = "{}/{}".format(server_upload, Common.basename(local))
            info = '{} {}'.format(language_data('UPLOAD'), local)
            DataInterface.update_infotext_data(ip, self.task, info, True)

            ret, err = SSHUtil.upload_file(ssh, local, remote, upload_back)
            if not ret:
                state, info = False, '{} {}'.format(language_data('UPLOAD', 'FAILED'), str(err))
                DataInterface.update_infotext_data(ip, self.task, info, False)
                return False
            size[1] += size[0]
            size[0] = 0

        # 所有文件上传成功才成功 #
        state = True
        DataInterface.update_infotext_data(ip, self.task, language_data('UPLOAD', 'SUCCESS'), True)
        return True

    def exec_start_impl(self, ip, uploads, params):
        if not self.lock(ip):
            # model_gate.exception_data.set_data(language_data('TASK', 'CONFLICT'))
            DataInterface.update_infotext_data(ip, self.task, language_data('TASK', 'CONFLICT'), False)
            return
        ssh = DataInterface.get_ssh_instance(ip)
        if not self.upload_file(ssh, ip, uploads):
            self.unlock(ip)
            return
        if ip in self.interrupt_ips:
            return
        DataInterface.update_progress_data(ip, self.upload_offset, True)
        DataInterface.update_infotext_data(ip, self.task, language_data('TASK', 'START'), True)
        self.start_task(ssh, params)
        self.check_result(ssh, ip)
        self.unlock(ip)

    def execute_start(self, data):
        ips, script, root, params, uploads = data
        self.init(script, root)
        for ip in ips:
            Common.create_thread(func=self.exec_start_impl, args=(ip, uploads[ip], params[ip]))
            logger.info("{} {} start, params:{}, uploads:{}".format(ip, self.task, params[ip], uploads[ip]))

    def execute_stop(self, data):
        ips, script = data
        self.init(script, True)
        self.interrupt_ips = ips
        for ip in ips:
            DataInterface.update_infotext_data(ip, self.task, language_data('TASK', 'STOP'), False)
            DataInterface.update_progress_data(ip, 100, False)
            self.stop_task(DataInterface.get_ssh_instance(ip))
            self.unlock(ip)
            logger.info("{} {} stop".format(ip, self.task))

    @classmethod
    def get_execute_out(cls, ssh, cmd):
        out = SSHUtil.exec_info(ssh, cmd, True)[0]
        if not out:
            return None
        return out.split(Global.G_INNER_SPLIT)[1].strip()


@Singleton
class ExecHandler(object):
    """ 直接执行类 """

    @classmethod
    def exec_start_impl(cls, ip, flag, cmd, root):
        info, err = SSHUtil.exec_info(DataInterface.get_ssh_instance(ip), cmd, root)
        if flag not in ['BOOL']:
            info = info.split(Global.G_INNER_SPLIT)[1]
        data = (ip, flag, info, err)
        model_gate.single_start_result_data.set_data(data)

    def execute_start(self, data):
        """ 批量任务, 所以类型flag只有一种 """
        logger.debug("[ExecHandler] start: {}".format(data))
        tasks, flag, root = data
        for ip, cmd in tasks.items():
            Common.create_thread(func=self.exec_start_impl, args=(ip, flag, cmd, root))

    @classmethod
    def exec_upload_impl(cls, src_list, ip, dest):
        def progress(current, total):
            curr_progress[0] = current
            curr_progress[1] = total

        def update(s):
            end_me = False
            _total = 1
            while True:
                if s == src:
                    c, t = curr_progress
                    if t != 1:
                        _total = t
                    progress_data = ('PROG', s, dest, int(c * 100 / t), c, t)
                else:    # 当前传输的不是s, 说明s已经传完(或者失败?) #
                    progress_data = ('PROG', s, dest, 100, _total, _total)
                    end_me = True
                _result_data = {ip: progress_data}
                model_gate.single_upload_result_data.set_data(_result_data)
                if end_me or stop_flag:
                    return
                Common.sleep(0.2)

        result_data, curr_progress, stop_flag = {}, [0, 1], False
        ssh = DataInterface.get_ssh_instance(ip)
        for src in src_list:
            ret, err = True, None
            Common.create_thread(update, args=(src,))
            if Common.is_file(src):
                ret, err = SSHUtil.upload_file_root(ssh, src, dest, progress)
            elif Common.is_dir(src):
                ret, err = SSHUtil.upload_dir_root(ssh, src, dest, progress)
            result_data = {ip: ('END', src, dest, ret, err)}
            model_gate.single_upload_result_data.set_data(result_data)

        stop_flag = True

    def execute_upload(self, data):
        logger.info("[ExecHandler] upload: {}".format(data))
        src_list, dest_map = data
        for ip, dest in dest_map.items():
            Common.create_thread(func=self.exec_upload_impl, args=(src_list, ip, dest))

    @classmethod
    def exec_download_impl(cls, src_list, ip, dest):
        def progress(current, total):
            curr_progress[0] = current
            curr_progress[1] = total

        def update(args=None):
            _total = 1
            while True:
                c, t = curr_progress
                if t != 1:
                    _total = t
                _result_data = {ip: ('PROG', src_list, dest, int(c * 100 / t), c, t)}
                model_gate.single_download_result_data.set_data(_result_data)
                if stop_flag or (c == t):
                    return
                Common.sleep(0.2)

        result_data, curr_progress, stop_flag = {}, [0, 1], False
        Common.create_thread(update)
        ret, err = SSHUtil.download_dir_root(DataInterface.get_ssh_instance(ip), src_list, dest, progress)

        stop_flag = True
        result_data = {ip: ('END', src_list, dest, ret, err)}
        model_gate.single_download_result_data.set_data(result_data)

    def execute_download(self, data):
        logger.info("[ExecHandler] download: {}".format(data))
        src_map, dest = data
        for ip, src_list in src_map.items():
            Common.create_thread(func=self.exec_download_impl, args=(src_list, ip, dest))

    @classmethod
    def close(cls, data=None):
        login_data = model_gate.login_state_data.get_data()
        if not login_data:
            return
        cmd = 'rm -rf {}'.format(model_gate.settings_data.server_dir)
        for _, state in login_data.items():
            if state['STATE'] == 'SUCCESS':
                SSHUtil.exec_ret(state['SSH'], cmd, True)


task_handler = TaskHandler()
exec_handler = ExecHandler()
